/*
Copyright 2010,2011 Matt Van Der Westhuizen

This file is part of CGLL.

CGLL is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

CGLL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with CGLL.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.chaoticengine.cgll.entity;

import org.chaoticengine.cgll.entity.component.ComponentManager;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.state.StateBasedGame;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.chaoticengine.cgll.collision.IBroadPhaseCollisionDetector;
import org.chaoticengine.cgll.entity.Entity.EntityType;
import org.chaoticengine.cgll.profile.Profile;
import org.chaoticengine.cgll.profile.ProfileManager;
import org.chaoticengine.cgll.ui.Widget;
import org.newdawn.slick.geom.Vector2f;

/**
 * An instance of the EntityManager class controls the activities of all
 * entities at a high level. Every CGLLGameState has a single EntityManager
 * associated with it, which is responsible for:
 *   -Calling the update(...) method on entities that require updating.
 *   -Calling the render(...) method on entities that require rendering.
 *   -Calling the broad-phase collision detection code once per game loop and
 *    calling narrow-phase collision handling for each collision detected.
 *   -Routing input events to the to the entities that need to respond to them.
 *
 * @author Matt v.d. Westhuizen
 */
public class EntityManager {
    /** List of all entities */
    private ArrayList<Entity> entities = null;

    /** List of entities that must be rendered */
    private ArrayList<Entity> renderEntities = null;

    /** List of entities that must be updated */
    private ArrayList<Entity> updateEntities = null;

    /** List of entities that can handle collisions */
    private ArrayList<Entity> collisionEntities = null;

    /** List of entities that must be destroyed */
    private ArrayList<Entity> destroyEntities = null;

    /** Keep count track of different types of entities. */
    private Map<EntityType, List<Entity>> entityGroups = null;

    /** The player avatar entity */
    private Entity player = null;
    
    /** Broad phase collision detector. */
    private IBroadPhaseCollisionDetector bpCd = null;

    /** Game container access point for any game logic classes. */
    private GameContainer container = null;

    /** Widgets to render this iteration. */
    private List<Widget> widgets = null;

    public EntityManager() {
        this.entities       = new ArrayList<Entity>();
        this.renderEntities = new ArrayList<Entity>();
        this.updateEntities = new ArrayList<Entity>();
        this.collisionEntities = new ArrayList<Entity>();
        this.destroyEntities = new ArrayList<Entity>();
        this.widgets = new ArrayList<Widget>();
        this.entityGroups = new HashMap<EntityType, List<Entity>>();

        for (int i = 0; i < EntityType.values().length; i++) {
            this.entityGroups.put(EntityType.values()[i], new ArrayList<Entity>());
        }
    }

    public Entity getPlayer() {
        return(player);
    }

    public IBroadPhaseCollisionDetector getCollisionDetector() {
        return bpCd;
    }

    public void setCollisionDetector(IBroadPhaseCollisionDetector bpCd) {
        this.bpCd = bpCd;
    }

    public GameContainer getContainer() {
        return container;
    }

    public void setContainer(GameContainer container) {
        this.container = container;
    }

    public void destroyEntity(Entity e) {
        destroyEntities.add(e);
    }

    public void addEntity(Entity e) {
        if (entities.contains(e)) {
            return;
        }
        
        e.setOwner(this);
        entities.add(e);
        entityGroups.get(e.getType()).add(e);
        updateCapabilities(e);
    }

    public void removeEntity(Entity e) {
        entities.remove(e);
        renderEntities.remove(e);
        updateEntities.remove(e);
        collisionEntities.remove(e);
        entityGroups.get(e.getType()).remove(e);
        if (bpCd != null) {
            bpCd.removeEntity(e);
        }
    }

    public void destroyAll() {
        for (int i = 0; i < entities.size(); i++) {
            Entity e = entities.get(i);
            destroyEntity(e);
        }
    }

    public void destroyAllOfType(EntityType type) {
        for (int i = 0; i < entities.size(); i++) {
            Entity e = entities.get(i);
            if (e.getType().equals(type)) {
                destroyEntity(e);
            }
        }
    }

    public void updateCapabilities(Entity e) {
        if (!entities.contains(e)) {
            return;
        }

        // Update capabilities
        ComponentManager components = e.getComponentManager();

        if (components.mustRender()) {
            if (!renderEntities.contains(e)) {
                renderEntities.add(e);
            }
        } else {
            renderEntities.remove(e);
        }

        if (components.mustUpdate()) {
            if (!updateEntities.contains(e)) {
                updateEntities.add(e);
            }
        } else {
            updateEntities.remove(e);
        }

        if (components.canCollide()) {
            if (!collisionEntities.contains(e)) {
                if (bpCd != null) {
                    bpCd.addEntity(e);
                }
                collisionEntities.add(e);
            }
        } else {
            if (bpCd != null) {
                bpCd.removeEntity(e);
            }
            collisionEntities.remove(e);
        }

        if (e.getType().equals(Entity.EntityType.PLAYER)) {
            this.player = e;
        }
    }

    public void render(GameContainer gc, StateBasedGame sbg, Graphics gr) {
        // Render entities
        for (int i = 0; i < renderEntities.size(); i++) {
            renderEntities.get(i).getComponentManager().render(gc, sbg, gr);
        }

        // Render player UI widgets
        if (this.player != null) {
            Iterator<Widget> wIt = this.widgets.iterator();
            int size = 32;
            int pad = 4;
            int posX = pad;
            int posY = 0;
            int rightPosX = gc.getWidth() - pad;

            Profile prof = ProfileManager.getInstance().getProfile();
            if ((prof != null) && (prof.getSettings().getWidgetsTop())) {
                posY = pad;
            } else {
                posY = gc.getHeight() - size - pad;
            }

            while (wIt.hasNext()) {
                Widget w = wIt.next();

                if (w.getAlignment() == Widget.Alignment.LEFT) {
                    int wHeight = (int) w.getBounds().getHeight();
                    w.render(new Vector2f(posX, posY + (size - wHeight) / 2), gc, sbg, gr);
                    posX += pad + w.getBounds().getWidth();
                } else if (w.getAlignment() == Widget.Alignment.RIGHT) {
                    int wHeight = (int) w.getBounds().getHeight();
                    rightPosX -= (int) w.getBounds().getWidth();
                    w.render(new Vector2f(rightPosX, posY + (size - wHeight) / 2), gc, sbg, gr);
                    rightPosX -= pad;
                }
            }
        }
    }

    public void update(GameContainer gc, StateBasedGame sb, int delta) {
        for (int i = 0; i < updateEntities.size(); i++) {
            updateEntities.get(i).getComponentManager().update(gc, sb, delta);
        }

        // Update widgets
        if (this.player != null) {
            this.widgets = player.getComponentManager().getWidgets();

            Iterator<Widget> wIt = this.widgets.iterator();
            while (wIt.hasNext()) {
                wIt.next().update();
            }
        }
    }

    public void doCollisions() {
        if (bpCd != null) {
            bpCd.doHitDetection();
        }
    }

    /**
     * Destroys any entities on which the Entity.destroy() method has been
     * called. This is handled as a separate phase in the game loop after the
     * update-render phase, to avoid situations in collision detection where
     * entities try to access other entities that have already been destroyed
     * (mutual annihilation in the case of a collision for example).
     */
    public void destroyEntities() {
        Iterator<Entity> it = destroyEntities.iterator();

        while (it.hasNext()) {
            Entity e = it.next();

            if (e.getType() == Entity.EntityType.PLAYER) {
                if (e == player) {
                    player = null;
                }
            }

            // Remove all entity references from this EntityManager
            removeEntity(e);

            // Destroy components
            e.getComponentManager().destroy();

            // Globally destroy all traces of this entity
            EntityFactory.getInstance().destroyEntity(e);
        }

        destroyEntities.clear();
    }

    public int getCount(EntityType type) {
        return(entityGroups.get(type).size());
    }
}
